import typing
from datetime import datetime, timedelta
from typing import Optional, Dict, List, Union
import re
from bson import ObjectId
from pydantic import BaseModel, parse_obj_as, validator, conint

from core.search import compose_mongo_query
from exceptions import InvalidParam
from models.administrative_division import AdministrativeDivision
from models.common import MongoModel
from .basic import AggregateResult


class SearchParam(BaseModel):
    current:  conint(ge=1, le=2**10) = 1
    size: conint(ge=0, le=50) = 10

    activity_type : Optional[str]

    sort: Optional[List[str]]
    filter: Optional[Dict]
    aggs: Optional[list] = list()
    search_func: Optional[str]

    offset: conint(ge=0, le=2**32) = 0

    def get_mongo_query(self, filter_type):
        """
        依赖于filter_type的定义，更容易定制化query，但是前端使用不灵活
        """
        if not self.filter:
            self.filter = {}
        search_filter = parse_obj_as(filter_type, self.filter)
        return search_filter.to_mongo_query()

    def initial_pagination(self):
        if not self.current:
            raise ValueError("current should be greater than 0")
        # if not self.size:
        #     raise ValueError("size should be greater than 0")
        self.offset = (self.current - 1) * self.size


class SearchResult(MongoModel):
    items: list = list()
    aggregation: Union[List[AggregateResult], Dict, typing.Any] = list()
    total: int = 0
    counts: int = 0


class BusinessFilter(MongoModel):
    ids: Optional[List[str]]
    name: Optional[str]
    level: Optional[str]
    ocodes: Optional[List[str]]

    is_deleted: bool = False

    def to_mongo_query(self):
        query = {}
        if self.ids:
            query['_id'] = {"$in": [ObjectId(i) for i in self.ids]}
        if self.name:
            _name = re.compile(re.escape(self.name.strip()))
            query['name'] = {"$regex": _name}
        if self.level:
            query[f'{self.level}_type.0'] = {"$exists": True}
        if self.ocodes:
            query['ocode'] = {"$in": self.ocodes}
        query["is_deleted"] = True if self.is_deleted else {"$ne": True}
        return query


class ActivityFilter(AdministrativeDivision):
    ids: Optional[List[str]]
    name: Optional[str]
    activity_type: Optional[str]
    business_ids: Optional[List[str]]
    institution_id: Optional[str]
    location: Optional[str]
    start_time: Optional[datetime]
    end_time: Optional[datetime]
    start_year: Optional[int]
    end_year: Optional[int]
    ocodes: Optional[List[str]]

    is_deleted: bool = False

    def to_mongo_query(self):
        query = {}
        if self.activity_type:
            query['activity_type'] = self.activity_type
            print(self.activity_type)
        if self.ids:
            query['_id'] = {"$in": [ObjectId(i) for i in self.ids]}
        if self.activity_type:
            query['activity_type'] = self.activity_type
        if self.name:
            _name = re.compile(re.escape(self.name.strip()))

            query['name'] = {"$regex": _name}
        # if self.business_ids:
        #     query['business_id'] = {"$in": [ObjectId(i) for i in self.business_ids]}
        if self.institution_id:
            query['institution_id'] = ObjectId(self.institution_id)

        if self.start_time:
            query['start_time'] = {"$gte": self.start_time}
        if self.end_time:
            query['end_time'] = {"$lte": self.end_time}
        if self.start_year:
            self.end_year = self.end_year + 1 if self.end_year else self.start_year + 1
            from_date = datetime(self.start_year, 1, 1) - timedelta(hours=8)
            to_date = datetime(self.end_year, 1, 1) - timedelta(hours=8)
            query['end_time'] = {"$gte": from_date,
                                 "$lt": to_date}
        if self.ocodes:
            query['ocode'] = {"$in": self.ocodes}

        query["is_deleted"] = True if self.is_deleted else {"$ne": True}

        if self.region:
            for k, v in self.region._iter(exclude_none=True):
                query[f"region.{k}"] = v

        return query


class RegisterRecordsFilter(BaseModel):
    activity_id: str
    is_attended: bool
    level_of_role: Optional[str]
    name: Optional[str]

    def to_mongo_query(self):
        _query = {"activity_id": ObjectId(self.activity_id), "is_attended": self.is_attended}
        if self.level_of_role:
            _query['level_of_role'] = self.level_of_role
        if self.name:
            _query['name'] = self.name
        return _query


class InstitutionRecordsFilter(BaseModel):
    activity_id: str
    is_attended: bool
    level_of_role: Optional[str]
    institution_name: Optional[str]
    in_kexie: Optional[bool] = None
    institution_type: Optional[str]

    def to_mongo_query(self):
        _query = {"activity_id": ObjectId(self.activity_id), "is_attended": self.is_attended}
        if self.institution_name:
            _query['institution_name'] = self.institution_name
        if self.in_kexie is not None:
            _query['in_kexie'] = self.in_kexie
        if self.institution_type:
            _query['institution_type'] = self.institution_type
        return _query


class BatchSearchParam(BaseModel):
    offset: Optional[int] = 0
    size: Optional[int] = 20
    fields: Optional[Union[List[str], Dict]] = None
    is_deleted: Optional[bool] = True
    sort: Optional[List[str]]

    @validator('offset', "size")
    def no_negative_number(cls, v):
        if v < 0:
            raise InvalidParam("offset/size", "offset/size should be positive number")
        return v


class QuerySearchParam(BatchSearchParam):
    query: Optional[dict] = {}

    def to_mongo_query(self):
        exclude_fields = set(BatchSearchParam.__fields__.keys()) | {"query"}
        update_query = self.dict(exclude=exclude_fields, exclude_unset=True)
        if self.query:
            update_query.update(self.query)
        return update_query

    def load_mongo_query(self, model):
        return compose_mongo_query(self.to_mongo_query(), model)
